/*******************************************************************************
 * Copyright (c) 2002 Boris Pruessmann and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Common Public License v0.5
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors:
 *    Boris Pruessmann - Initial implementation.
 ******************************************************************************/
package net.sourceforge.perforce.core.internal.events;

import java.util.ArrayList;
import java.util.List;

import net.sourceforge.perforce.core.IResourceStateChangeListener;
import net.sourceforge.perforce.core.PerforceCorePlugin;
import net.sourceforge.perforce.core.PerforceException;
import net.sourceforge.perforce.core.PerforceSynchronizer;
import net.sourceforge.perforce.core.api.IPerforceServer;
import net.sourceforge.perforce.core.internal.P4TeamProvider;
import net.sourceforge.perforce.core.resources.IChangelist;
import net.sourceforge.perforce.core.syncinfo.IResourceStateDelta;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.team.core.RepositoryProvider;

/**
  * @version $Revision: 68 $
  * @author  <a href="mailto:bpruessmann@users.sourceforge.net">Boris Pruessmann</a>
  */
public class AddDeleteMoveListener 
	implements IResourceChangeListener, IResourceStateChangeListener, IResourceDeltaVisitor
{	
	// ---------------------------------------------------------------------------- Public Constants

	/** Id of Perforce marker category. */
	public static final String P4_MARKER = "net.sourceforge.perforce.core.p4marker";   //$NON-NLS-1$
	/** Id of addition markers. */
	public static final String ADDITION_MARKER = "net.sourceforge.perforce.core.p4add";//$NON-NLS-1$
	
	// ----------------------------------------------------------------------- Public Static Methods
	
	/** Called to refresh all perforce markers.
	  *
	  * @throws CoreException in case of an error. 
	  */
	public static void refreshAllMarkers() 
		throws CoreException 
	{
		IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
		for (int i = 0; i < projects.length; i++) 
		{
			IProject project = projects[i];
			if (RepositoryProvider.getProvider(project, PerforceCorePlugin.getTypeId()) != null) 
			{
				refreshMarkers(project);
			}
		}		
	}

	/** Called to clear all perforce markers.
	  *
	  * @throws CoreException in case of an error. 
	  */
	public static void clearAllPerforceMarkers() 
		throws CoreException 
	{
		IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
		for (int i = 0; i < projects.length; i++) 
		{
			IProject project = projects[i];
			if (RepositoryProvider.getProvider(project, PerforceCorePlugin.getTypeId()) != null) 
			{
				clearPerforceMarkers(project);
			}
		}
	}
	
	
	// ------------------------------------------------------ Public Methods (IResourceDeltaVisitor)

	/** @see IResourceDeltaVisitor#visit(IResourceDelta) */
	public boolean visit(IResourceDelta delta) 
		throws CoreException 
	{
		try
		{
			IResource resource = delta.getResource();
			switch (delta.getKind())
			{
				case IResourceDelta.ADDED:
					handleResourceAdded(resource);
					break;
					
				case IResourceDelta.REMOVED:
					handleResourceRemoved(resource);
					break;
			}
			
			return true;
		}
		catch (PerforceException e)
		{
			throw new CoreException(e.getStatus());
		}
	}

	// ---------------------------------------------------- Public Methods (IResourceChangeListener)

	/** @see IResourceChangeListener#resourceChanged(IResourceChangeEvent) */
	public void resourceChanged(IResourceChangeEvent event) 
	{
		try
		{
			if (event.getType() == IResourceChangeEvent.POST_AUTO_BUILD)
			{
				IResourceDelta root = event.getDelta();
				IResourceDelta[] projectDeltas = root.getAffectedChildren();
				for (int i = 0; i < projectDeltas.length; i++) 
				{
					IResourceDelta delta = projectDeltas[i];
					IResource resource = delta.getResource();
					
					RepositoryProvider provider = 
						RepositoryProvider.getProvider(
							resource.getProject(), 
							PerforceCorePlugin.getTypeId());	
					if (provider != null)
					{
						delta.accept(this);
					}
				}
			}
			else
			if (event.getType() == IResourceChangeEvent.PRE_DELETE)
			{
				IResource resource = event.getResource();
				RepositoryProvider provider = 
					RepositoryProvider.getProvider(
						resource.getProject(), 
						PerforceCorePlugin.getTypeId());	
				if (provider != null)
				{	
					resource.accept(new IResourceVisitor() 
					{
						public boolean visit(IResource resource) 
							throws CoreException
						{
							try
							{
								handleResourceRemoved(resource);
							}
							catch (PerforceException e)
							{
								throw new CoreException(e.getStatus());
							}
							return true;
						}
					});
				}
			}
		}
		catch (CoreException e)
		{
			PerforceCorePlugin.getPlugin().log(e.getStatus());
		}							
	}
	
	// ----------------------------------------------- Public Methods (IResourceStateChangeListener)
	
	/** @see IResourceStateChangeListener#resourceStateChanged(IResourceStateDelta) */
	public void resourceStateChanged(IResourceStateDelta[] resourceStateDelta)
		throws PerforceException 
	{
		IResource[] resources = new IResource[resourceStateDelta.length];
		for (int i = 0; i < resourceStateDelta.length; i++)
		{
			resources[i] = resourceStateDelta[i].getResource();
		}
		
		try
		{
			createNecessaryMarkers(resources);
		}
		catch (CoreException e)
		{
			throw new PerforceException(e.getStatus());
		}
	}

	/** @see IResourceStateChangeListener#changelistStateChanged(IChangelist) */
	public void changelistStateChanged(IChangelist[] changelists) 
	{
	}
	
	/** @see IResourceStateChangeListener#changelistStateChanged(IPerforceServer) */
	public void changelistStateChanged(IPerforceServer[] servers) 
	{
	}
	
	// ---------------------------------------------------------------------- Private Static Methods
	
	/** Call this method to refresh the perforce markers of the given resource.
	  *
	  * @param resource the resource.
	  * @throws CoreException in case of an error. 
	  */
	private static void refreshMarkers(IResource resource) 
		throws CoreException
	{
		final List resources = new ArrayList();
		clearPerforceMarkers(resource);
		resource.accept(new IResourceVisitor() 
		{
			public boolean visit(IResource resource) 
			{
				if (resource.getType() != IResource.PROJECT) 
				{ 
					resources.add(resource);
				}
				
				return true;
			}
		}, IResource.DEPTH_INFINITE, true);
		
		createNecessaryMarkers((IResource[]) resources.toArray(new IResource[resources.size()]));
	}
	
	/** Call this method to clear the perforce markers of the given resource.
	  *
	  * @param resource the resource.
	  * 
	  * @throws CoreException in case of an error. 
	  */
	private static void clearPerforceMarkers(IResource resource) 
		throws CoreException 
	{
		IMarker[] markers = resource.findMarkers(P4_MARKER, true, IResource.DEPTH_INFINITE);
		for (int i = 0; i < markers.length; i++) 
		{
			markers[i].delete();
		}
	}
	
	/** Retrieves the addition marker of the specified resourec.
	  *
	  * @param resource the resource.
	  * @return the addition marker or null. 
	  * 
	  * @throws CoreException in case of an error.
	  */
	private static IMarker getAdditionMarker(IResource resource) 
		throws CoreException 
	{
   		IMarker[] markers = resource.findMarkers(ADDITION_MARKER, false, IResource.DEPTH_ZERO);
   		if (markers.length == 1) 
   		{
   			return markers[0];
   		}
   		
		return null;
	}

	/** Creates a new addition marker for the given resource.
	  *
	  * @param resource the resource.
	  * @return the addition marker.
	  */
	private static IMarker createAdditonMarker(IResource resource) 
	{
		if (!PerforceCorePlugin.getPlugin().getShowTasksOnAdd()) 
		{
			return null;
		}
		
		try 
		{
			IMarker marker = getAdditionMarker(resource);
			if (marker != null) 
			{
				return marker;
			}
			
			marker = resource.createMarker(ADDITION_MARKER);
			marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO);
			marker.setAttribute(IMarker.MESSAGE, "Local addition not under Perforce control");
			
			return marker;
		} 
		catch (CoreException e) 
		{
			PerforceCorePlugin.getPlugin().log(e.getStatus());
		}
		
		return null;
	}

	/** Checks the given resources and creates markers when necessary.
	  *
	  * @param changedResources array of resources.
	  * 
	  * @throws CoreException in case of an error. 
	  */
	private static void createNecessaryMarkers(IResource[] changedResources)
		throws CoreException
	{
		for (int i = 0; i < changedResources.length; i++) 
		{
			IResource resource = changedResources[i];
			
			P4TeamProvider provider = 
				(P4TeamProvider) RepositoryProvider.getProvider(
					resource.getProject(), 
					PerforceCorePlugin.getTypeId());	
			if (provider == null) 
			{
				break;
			}
			
			if (provider.hasRemote(resource))
			{
				IMarker marker = getAdditionMarker(resource);
				if (marker != null)
				{
					marker.delete();
				}
			}
			else
			{
				if (provider.hasRemote(resource.getParent()))
				{
					IMarker marker = getAdditionMarker(resource);
					if (marker == null)
					{
						createAdditonMarker(resource);
					}
				}
			}
		}
	}

	// ----------------------------------------------------------------------------- Private Methods

	/** Called to handle resource adds.
	  *
	  * @param resource the added resource.
	  * 
	  * @throws CoreException in case of an error. 
	  */
	private void handleResourceAdded(IResource resource) 
		throws CoreException
	{
		if (resource.getType() == IResource.FILE)
		{
			createNecessaryMarkers(new IResource[] { resource });
		}
	}

	/** Called to handle resource deletions.
	  *
	  * @param resource the removed resource.
	  * 
	  * @throws PerforceException in case of an error. 
	  */
	private void handleResourceRemoved(IResource resource)
		throws PerforceException
	{
		PerforceSynchronizer synchronizer = PerforceCorePlugin.getSynchronizer();
		if (resource.getType() == IResource.FILE)
		{
			synchronizer.deleteResourceSync(resource);
		}
		else
		{
			synchronizer.deleteFolderSync((IContainer) resource);
		}
	}
}
